package org.zhuzx.util;

import java.io.IOException;

/**
 * 基础重试器，内部实现重试逻辑，需要子类提供具体的执行逻辑。
 * @author	zhuzx	2017年9月19日
 */
public abstract class AbstractRetrier<E> {
	
	private int numberOfRetries;
	private long pauseTime;
	
	/**
	 * 根据指定的重试次数与暂停时间创建重试器
	 * @param numberOfRetries 允许的最大重试次数
	 * @param pauseTime 每次重试前的暂停时间（等待IO环境恢复），单位毫秒。
	 */
	protected AbstractRetrier(int numberOfRetries, long pauseTime) {
		this.numberOfRetries = numberOfRetries;
		this.pauseTime = pauseTime;
	}
	
	/**
	 * 执行逻辑，由子类实现。
	 * @param args 子类通常需要将参数取出后进行类型转换才能使用
	 * @return 返回类型与子类声明的泛型参数一致
	 * @throws IOException 只有抛出IO异常（及其子异常）的逻辑才有重试的必要
	 */
	protected abstract E execute(Object... args) throws IOException;
	
	/**
	 * 捕获IO异常并自动重试，重试未果则抛出运行时异常。
	 */
	public E executeByRetry(Object... args) {
		try {
			return execute(args);
		} catch (IOException e) {
			System.out.println("捕获【" + e.getClass().getSimpleName() + ":" + e.getMessage() + "】，开始重试。");
			for (int count=1; count<=numberOfRetries; count++) {
				try {
					Thread.sleep(pauseTime);
					return execute(args);
				} catch (InterruptedException e1) {
					Thread.currentThread().interrupt();
				} catch (IOException e2) {}
			}
			throw new RuntimeException("重试" + numberOfRetries + "次全部失败！", e);
		}
	}
	
	/**
	 * 捕获特定种类的IO异常并自动重试，重试未果则抛出运行时异常。
	 * @param expectantExceptionType 只针对该种异常进行重试，其余种类直接抛出运行时异常。
	 */
	public E executeByRetry(Class<? extends IOException> expectantExceptionType, Object... args) {
		try {
			return execute(args);
		} catch (IOException e) {
			if (expectantExceptionType.isInstance(e)) {
				for (int count=1; count<=numberOfRetries; count++) {
					try {
						Thread.sleep(pauseTime);
						return execute(args);
					} catch (InterruptedException e1) {
						Thread.currentThread().interrupt();
					} catch (IOException e2) {
						if (!expectantExceptionType.isInstance(e2)) {
							throw new RuntimeException("于第" + count + "次重试时出现预期外的异常", e2);
						}
					}
				}
				throw new RuntimeException("重试" + numberOfRetries + "次全部失败！", e);
			} else {
				throw new RuntimeException(e);
			}
		}
	}
	
}